//888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
//
//	File		 :	ParseMSF.cpp
//	Contents	 :	Source file for class ParseMSF
//	Creation Date:	22/12/1999
//	Last Modified:	23/12/1999	lg
//	tabs width	 :	every 4 characters
//
//888888888888888888888888888888888888888888888888888888888888888888888888888888888888888


#include <stdexcept>
#include <cassert>
#include <string>
#include <vector>
#include <sstream>
#include <string>
#include <algorithm>
#include <iostream>
#include "parsemsf.h"
#include "msfresource.h"
#include "std_helper.h"


//_______________________________________________________________________________________
//
//	Constructor
//
//		Just initializes variables
//		Last modified: lg 23/12/1999
//		
//_______________________________________________________________________________________
CParseMSF::CParseMSF(CParseMessageCallback& callback, bool ignoreLoop, bool consolidate)
: CParse(callback, IDS_ERR_INVALID_MSFFILE, IDS_ERR_MSFFILE),
		m_IgnoreLoop(ignoreLoop), m_Consolidate(consolidate)
{
	
}

//_______________________________________________________________________________________
//
//	Do Parse
//
//		Calls internal functions to parse Data_Stream into Sequences of amino acids
//		Input		: Data_Stream
//		Output		: outputSequence	
//		Last modified: lg 23/12/1999
//		
//_______________________________________________________________________________________
bool CParseMSF::DoParse(char* const data_Stream, CSequence& seq)
{
	assert(data_Stream);

	if (seq.FormatType == CSequence::eDSC)
		SetErrMsges(IDS_ERR_INVALID_DSCFILE, IDS_ERR_DSCFILE);
	else if (seq.FormatType == CSequence::ePHD)
		SetErrMsges(IDS_ERR_INVALID_PHDFILE, IDS_ERR_PHDFILE);

		
	// save positin along data stream
	Data_Stream = data_Stream;

	// clear all variables
	LineNumber = 0;
	seq.Clear();

	// abort process if errors
	if(	!ParseMSFHeader(seq) ||
		!ParseMSFNames(seq)	||
		!ParseMSFData(seq)	)
		return false;



	if (seq.FormatType == CSequence::eDSC)
	{
		bool isDSC = CheckDSCSecStruct(seq);
		// "The DSC secondary structure data could not be located in any of the %d lines. "
		// "This will be scanned as a standard MSF file."
		if ( ParseWarn(!isDSC, IDS_ERR_DSC_NODATA, LineNumber ) )
			return false;

		if (!isDSC)	
			seq.FormatType = CSequence::eMSF;
		else
			ArrangePredictionData(seq);
	}
		
	return true;
}


//_______________________________________________________________________________________
//
//	ParseMSFHeader
//
//		Internal function called by DoParse
//		locates and parses the MSF format header from Data_Stream
//		Data_Stream is advanced to the end of the header section.
//		Last modified: lg 23/12/1999
//		
//_______________________________________________________________________________________
bool CParseMSF::ParseMSFHeader(CSequence& outputSequence)
{
	// Loop through lines until "MSF:" keyword found
	// Search line by line for better diagnostics
	char* nextLine = Data_Stream;
	Data_Stream = 0;
	bool headerFound = false;
	while (nextLine)
	{
		PrepareNextLine(nextLine, Data_Stream, LineNumber);
		
		if (char * found = strstr(Data_Stream, "MSF:"))
		{
			// relax MSF searching to handle DSC files
			// if no "Type:" on this line and "MSF:" elsewhere use next line with MSF
			if (strstr(Data_Stream, "Type:") == 0 && nextLine && strstr(nextLine+1, "MSF:"))
				continue;
			Data_Stream = found + 4;
			headerFound = true;
			break;
		}
	}

	// check if at stream end
	//"MSF files should contain the keyword \"MSF\". \r\n"
	//"This was not found in any of the %d lines."
	if (ParseErr(!headerFound, IDS_ERR_MSF_KEYunsigned_MISSING, -(int)LineNumber))
		return false;

	// check line ends in ".."
	char * found = strstr(Data_Stream, "..");
	// "Line %d should end with a pair of full stops (\"..\")."
	if (ParseWarn(!found, IDS_WARN_MSF_MISSINGFULLSTOPS, LineNumber))
		return false;
	if (found)
	{
		// increment position past the periods ".."
		found += 2;
		while (found < nextLine)
		{	
			if (ParseWarn(!isspace(*found++), IDS_WARN_MSF_MISSINGFULLSTOPS,
																	LineNumber))
				return false;
		}
	}
	
	// use c++ streams for convenience
	std::istringstream lineStrm(Data_Stream);
	std::string token;

	// get and check sequence length
	lineStrm >> token;
	outputSequence.Len = atoi(token.c_str());
	// "The alignment length given on line %d of the MSF header was much too large. ("MSF:  %s")"
	if (ParseErr(outputSequence.Len > 32000, IDS_ERR_MSF_SEQUENCETOOLONG,
												LineNumber, token.c_str()))
		return false;

	
	// check presence of sequence type
	lineStrm >> token;
	// "Line %d should contain the keyword "Type:""
	if (ParseErr(stricmp(token, "Type:") != 0, IDS_ERR_MSF_TYPE_MISSING,
																		LineNumber))
		return false;
	
	// check correct sequence type
	lineStrm >> token;
	// "The Sequence Type on line %d should be given as "Type: P" (protein), "
	// "instead of "Type: %s"."
	if (ParseErr(stricmp(token, "P") != 0, IDS_ERR_MSF_TYPE,
													LineNumber, token.c_str()))
		return false;

	// move to next line
	Data_Stream = nextLine ? nextLine + 1 : 0;
	return true;
}

//_______________________________________________________________________________________
//
//	ParseMSFNames
//
//		Internal function called by DoParse
//		locates and parses the MSF Sequence Names from Data_Stream into outputSequence
//		Data_Stream is advanced to the end of the names section.
//		Last modified: lg 23/12/1999
//		
//_______________________________________________________________________________________
bool CParseMSF::ParseMSFNames(CSequence& outputSequence)
{
	// get line number for diagnostic purposes
	char* nextLine = Data_Stream;
	Data_Stream = 0;
	while (nextLine)
	{
		PrepareNextLine(nextLine, Data_Stream, LineNumber);
		
		// skip past initial whitespace
		SkipSpaces(Data_Stream);
		
		// end of names section if // encountered
		if (strncmp(Data_Stream, "//", 2) == 0)
			break;


		// skip past lines not containing lines
		if (stricmp(std::string(Data_Stream, 5), "Name:") != 0)
			continue;

		Data_Stream += 5;

		// skip past next whitespace
		SkipSpaces(Data_Stream);

		// skip to end of non-whitespace name
		char* nameEnd = Data_Stream;
		SkipToSpace(nameEnd);
		*nameEnd = '\0';

		// check to make sure it is shorter than 15 characters
		// "Line %d contains the Sequence Name \"%s\""
		// " which is longer than the allowed 15 characters."
		if (ParseWarn(nameEnd - Data_Stream > 15, IDS_WARN_MSF_NAMETOOLONG,
														LineNumber, Data_Stream))
			return false;

		// add name to list of names
		outputSequence.Names.push_back(std::string(Data_Stream, nameEnd - Data_Stream));

	}


	// "It ended prematurely after %d lines without any sequence residue data."
	if (ParseErr(!nextLine, IDS_ERR_MSF_NODATA, -(int)LineNumber))
		return false;

	// test if all names are unique
	unsigned cnt = outputSequence.CheckDuplicateNamesCnt();
	// "All Sequence Names must be unique. There were %d duplicate(s)."
	if (ParseErr(cnt != 0, IDS_ERR_MSF_DUPLICATENAMES, -(int)cnt))
		return false;
	
	// add sequence Data items corresponding to the names
	outputSequence.Data.assign(outputSequence.Names.size(), std::string());

	
	// get next line	
	Data_Stream = nextLine + 1;

	return true;
}

//_______________________________________________________________________________________
//
//	ParseMSFData
//
//		Internal function called by DoParse
//		Loop through lines getting sequence data corresponding to each name until the end
//			of the Data_Stream
//		Data_Stream is advanced all the while
//		Last modified: lg 23/12/1999
//		
//_______________________________________________________________________________________
bool CParseMSF::ParseMSFData(CSequence& outputSequence)
{
	
	// index of last matching name, -1 indicates null
	int last_NameMatch = 0;

	char* nextLine = Data_Stream - 1;
	while (nextLine)
	{
		// get next line
		PrepareNextLine(nextLine, Data_Stream, LineNumber);
		
		// skip past initial whitespace
		SkipSpaces(Data_Stream);

		// the msf part of phd files are delimited by 80 underscores
		if (outputSequence.FormatType== CSequence::ePHD &&
			CheckMatchChars(Data_Stream, '_', 70))
				break;


		// discard blank line
		if (!*Data_Stream)
			continue;

		// skip to next whitespace
		char* nameEnd = Data_Stream;
		SkipToSpace(nameEnd);
		assert(nameEnd != Data_Stream);
		// nameEnd points to one past name


		// skip past lines not beginning with one of the sequence names
		std::string name(Data_Stream, nameEnd-Data_Stream);
		if (-1 == MatchNames(outputSequence.Names, name, last_NameMatch))
			continue;

		Data_Stream = nameEnd;


		unsigned lineLength = 0;

		// reserve space for characters, improves speed
		outputSequence.Data[last_NameMatch].reserve(
					outputSequence.Data[last_NameMatch].length() + 70);

		// Add all non-whitespace
		while (*Data_Stream)
		{
			if(!isspace(*Data_Stream))
			{
				outputSequence.Data[last_NameMatch] += *Data_Stream;
				++lineLength;
			}
			++Data_Stream;
		}

		// "The sequence on Line %d contains too many amino acids."
		if (ParseWarn(lineLength > 50, IDS_WARN_MSF_TOOMANYAA, LineNumber))
			return false;
	}

	// exit if  empty data
	for (int i = outputSequence.Count() - 1; i >= 0; --i)
		// "No data in any of the %d lines could be matched to the sequence "%s""
		if (ParseErr(outputSequence.Data[i].length() == 0,
				IDS_ERR_MSF_SEQLENZERO, -(int)LineNumber,	outputSequence.Names[i].c_str()))
			return false;

	if (!outputSequence.CheckLengthsEq())
	{
		ParseErrLength(outputSequence);
		return false;
	}

	unsigned dataLen = outputSequence.Data[0].length();
	char buf[20];
	sprintf(buf, "%d", dataLen);
	// "The Sequence Length given in the file header (%d)\r\n"
	// " did not match the real Sequence Lengths (%s)!"
	if (ParseWarn(outputSequence.Len != dataLen, IDS_WARN_MSF_SEQLENMISMATCH,
															-(int)outputSequence.Len, buf))
		return false;
	outputSequence.Len = dataLen;

	// get next line	
	if (nextLine)
		Data_Stream = nextLine + 1;
	else
		Data_Stream = 0;
		
	return true;
}



//_______________________________________________________________________________________
//
//	CheckDSCSecStruct
//
//		Internal function called by DoParse
//		Checks if this is a valid DSC file
//		Last modified: lg 11/04/2000
//		
//_______________________________________________________________________________________
bool CParseMSF::CheckDSCSecStruct(CSequence& seq)
{
	int secondaryRow = seq.Count() - 4;
	if (secondaryRow > 0)
	{
		if(	seq.Names[secondaryRow  ] == "DSC" &&
			seq.Names[secondaryRow+1] == "P_H" &&
			seq.Names[secondaryRow+2] == "P_E" &&
			seq.Names[secondaryRow+3] == "P_C")
			return true;
		if(	seq.Names[secondaryRow  ] == "DSC_SEC"	&&
			seq.Names[secondaryRow+1] == "PROB_H"	&&
			seq.Names[secondaryRow+2] == "PROB_E"	&&
			seq.Names[secondaryRow+3] == "PROB_C"	)
			return true;
	}
	seq.FormatType = CSequence::eMSF;
	return false;
}



//_______________________________________________________________________________________
//
//	ArrangePredictionData()
//
//		Description	 : optionally merges and erases prediction data
//		Output		 : outputSequence
//		Last modified: lg 04/4/1999
//
//_______________________________________________________________________________________
void CParseMSF::ArrangePredictionData(CSequence& seq)
{
	int secondaryRow = seq.Count() - 4;
	unsigned secLinesCnt = m_Consolidate ? 1 : 4;
	if (m_IgnoreLoop)
	{
		std::replace(seq.Data[secondaryRow].begin(), seq.Data[secondaryRow].end(), 'C', '.');
		seq.Data.pop_back();
		seq.Names.pop_back();
		if (secLinesCnt == 4)
			--secLinesCnt;
	}
	// copy names => Extra names
	seq.ExtraNames.insert(seq.ExtraNames.begin(),
							seq.Names.begin() +secondaryRow,
							seq.Names.begin() +secondaryRow + secLinesCnt);
	seq.Names.erase(seq.Names.begin() + secondaryRow, seq.Names.end());

	// copy data => extra data
	seq.ExtraData.insert(seq.ExtraData.begin(),
							seq.Data.begin() +secondaryRow,
							seq.Data.begin() +secondaryRow + secLinesCnt);
	seq.Data.erase(seq.Data.begin() + secondaryRow, seq.Data.end());
	
}	
